/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.interaction3d.assembly.convert.obj;

import java.io.BufferedReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.interaction3d.assembly.convert.Material;

import static java.lang.Double.parseDouble;
import static java.lang.Integer.parseInt;


/**
 * http://local.wasp.uwa.edu.au/~pbourke/dataformats/mtl/
 * @author Michael Nischt
 */
public class ConverterMTL
{
    private final Map<String, Material> materials = new HashMap<String, Material>();
    private Material material;

    public static void main(String... args) throws Exception
    {
        String desktop = System.getProperty("user.home") + File.separator + "Desktop" + File.separator;
        String target = "/home/micha/Projects/Cam3D/code/src/main/resources/de/tuberlin/qu/interaction3d/campnou/";

        String dst = target + "material";
        String src = desktop + "camp_nou_obj" + File.separator + "camp_nou.mtl";

        convertFile(src, dst);
    }

    public static void convertFile(String src, String dst)
    {
        File dstFile = new File(dst);
        if(!dstFile.isDirectory() && !dstFile.mkdirs())
        {
            throw new IllegalArgumentException("Directory '" + dst + "' does not exist and cannot be created");
        }

        ConverterMTL convert = new ConverterMTL();
        convert.convert(new File(src));

        for(Map.Entry<String, Material> entry : convert.materials.entrySet())
        {
            FileOutputStream out = null;
            try
            {
                out = new FileOutputStream(new File(dst + File.separator + entry.getKey() + ".material.xml"));
                entry.getValue().writeTo(out);
            }
            catch (IOException ex)
            {
                log(ex);
            }
            finally
            {
                try
                {
                    out.close();
                }
                catch (IOException ex)
                {
                    log(ex);
                }
            }
        }
    }

    // <editor-fold defaultstate="collapsed" desc="convert">

    public void convert(String uri)
    {
        try
        {
            InputStream in = URI.create(uri).toURL().openStream();
            try
            {
                convert(in);
            }
            finally
            {
                close(in);
            }
        }
        catch (IOException ex)
        {
            Logger.getLogger(ConverterMTL.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void convert(InputStream in)
    {
        try
        {
            BufferedReader reader = new BufferedReader(new InputStreamReader(in));
            convert(reader);
        }
        catch (IOException ex)
        {
            log(ex);
            throw new RuntimeException(ex);
        }
    }

    public void convert(File f)
    {
        try
        {
            BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(f)));
            convertAndClose(reader);
        }
        catch (IOException e)
        {
            log(e);
            throw new RuntimeException(e);
        }
    }

    private void convertAndClose(BufferedReader reader) throws IOException
    {
        try
        {
            convert(reader);
        }
        finally
        {
            close(reader);
        }
    }

    static private void close(Closeable c)
    {
        try
        {
            c.close();
        }
        catch (IOException e)
        {
            log(e);
            throw new RuntimeException(e);
        }
    }

    // </editor-fold>

    private void convert(BufferedReader reader) throws IOException
    {
        String line;

        while((line=reader.readLine()) != null)
        {
            line(line);
        }
    }

    // <editor-fold defaultstate="collapsed" desc="line">

    private void line(String line)
    {
        {   // get rid of comment
            int index = line.indexOf('#');
            if (index >= 0)
            {
                line = line.substring(0, index);
            }
        }
        line = line.trim();

        StringTokenizer scanner = new StringTokenizer(line);

        //empty line
        if (!scanner.hasMoreElements())
        {
            return;
        }

        String op = scanner.nextToken();


        if (op.equals("newmtl"))
        {
            newmtl(scanner.nextToken());
        }
        else if (op.equals("Ka"))
        {
            constant("ambient", scanner);
        }
        else if (op.equals("Kd"))
        {
            constant("diffuse", scanner);
        }
        else if (op.equals("Ks"))
        {
            constant("specular", scanner);
        }
        else if (op.equals("Ns"))
        {
            constant("shininess", scanner);
        }
        else if (op.equals("d"))
        {
            constantDissolve("transparency", scanner);
        }
        else if (op.equals("map_Ka"))
        {
            textureMap("ambient", scanner);
        }
        else if (op.equals("map_Kd"))
        {
            textureMap("diffuse", scanner);
        }
        else if (op.equals("map_Ks"))
        {
            textureMap("specular", scanner);
        }
        else if (op.equals("map_Ns"))
        {
            textureMap("shininess", scanner);
        }
        else if (op.equals("map_d"))
        {
            textureMap("transparency", scanner);
        }
        else if (op.equals("Tf"))
        {
            log("Transmission Filter (Tf) is not supported");
        }
        else if (op.equals("illum"))
        {
            illum(parseInt(scanner.nextToken()));
        }
        else if (op.equals("sharpness"))
        {
            log("Sharpness of the reflections (sharpness) is not supported");
        }
        else if (op.equals("Ni"))
        {
            log("Optical surface density (Ni) is not supported");
        }
        else
        {
            log("Unknown: " + op);
        }
    }

    // </editor-fold>

    private void newmtl(String name)
    {
        material = new Material("Blender");
        materials.put(name, material);
    }

    private void constant(String name, StringTokenizer scanner)
    {
        // r (g=r b=r)
        // spectral file.rfl (factor=1)
        // xyz x y z

        String r = scanner.nextToken();
        if(r.equals("spectral"))
        {
            log(String.format("Spectral files are not supported (%s)", name));
        }
        else if(r.equals("xyz"))
        {
            log(String.format("CIEXYZ color space is not supported (%s)", name));
        }
        else
        {
            double[] consant;
            if(scanner.hasMoreTokens())
            {
                // rgb
                consant = new double[]
                {
                    parseDouble(r),
                    parseDouble(scanner.nextToken()),
                    parseDouble(scanner.nextToken())
                };
            }
            else
            {
                // luminance
                consant = new double[]
                {
                    parseDouble(r)
                };
            }
            material.constants.put(name, consant);
        }
    }

    private void constantDissolve(String name, StringTokenizer scanner)
    {
        // (-halo) factor (1=opaue, 0=transparent)
        // dissolve = 1 - (N*v)(1-factor)
        String d = scanner.nextToken();

        if(scanner.hasMoreTokens())
        {
            StringBuilder sb = new StringBuilder(d);
            d = scanner.nextToken();

            while(scanner.hasMoreTokens())
            {
                sb.append(" ").append(d);
                d = scanner.nextToken();
            }

            log(String.format("d options (%s) are supported", sb.toString()));
        }

        material.constants.put("transparency", new double[] { parseDouble(d) });
    }

    private void textureMap(String name, StringTokenizer scanner)
    {
        // (-option (args))* filename
        //    -bm mult
        //    -clamp on | off
        //    -blendu on | off
        //    -blendv on | off
        //    -imfchan r | g | b | m | l | z
        //    -mm base gain
        //    -o u v w
        //    -s u v w
        //    -t u v w
        //    -texres value

        String map = scanner.nextToken();

        if(scanner.hasMoreTokens())
        {
            StringBuilder sb = new StringBuilder(map);
            map = scanner.nextToken();

            while(scanner.hasMoreTokens())
            {
                sb.append(" ").append(map);
                map = scanner.nextToken();
            }

            log(String.format("Map options (%s) are supported", sb.toString()));
        }

        material.textures.put(name, map);
    }
    
    private void illum(int id )
    {
        // illum_#
        // 0	Color on and Ambient off
        // 1	Color on and Ambient on
        // 2	Highlight on
        // 3	Reflection on and Ray trace on
        // 4	Transparency: Glass on
        //      Reflection: Ray trace on
        // 5	Reflection: Fresnel on and Ray trace on
        // 6	Transparency: Refraction on
        //      Reflection: Fresnel off and Ray trace on
        // 7	Transparency: Refraction on
        //      Reflection: Fresnel on and Ray trace on
        // 8	Reflection on and Ray trace off
        // 9	Transparency: Glass on
        //      Reflection: Ray trace off
        // 10	Casts shadows onto invisible surfaces
        log(String.format("illum (%d) is not supported", id));
    }

    // <editor-fold defaultstate="collapsed" desc="log">

    static private void log(String msg)
    {
        Logger.getLogger(ConverterMTL.class.getName()).log(Level.WARNING, msg);
    }

    static private void log(Throwable thrown)
    {
        log(null, thrown);
    }

    static private void log(String msg ,Throwable thrown)
    {
        Logger.getLogger(ConverterMTL.class.getName()).log(Level.SEVERE, msg, thrown);
    }

    // </editor-fold>
}

